package tasks;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Random;

public class ConfigurationManager {
    //manufacturers
    public static final byte INTEL = 4;
    public static final byte HP = 2;
    public static final byte COMP = 1;
    
    //quality
    public static final byte BEST = 4;
    public static final byte GOOD = 2;
    public static final byte AVG = 1;
    
    public static final byte LG_NUM_C = 3;
    public static final byte LG_NUM_M = 3;
    public static final byte B_SIZE = LG_NUM_C + LG_NUM_M;
    public static final byte C_SIZE = B_SIZE + 10;
    
    public static final int C_NOT_SPECIFIED = 7;
    public static final int P_NOT_SPECIFIED = 1000;
    
    public static final String SEPARATOR = ",";

    private static Random rand = new Random();
    
    private static final double P_CUT = 0.2;
    
    // Arbitrary coeficients used to generate prices
    private double coef;
    
    public static final int MAX_PRICE = 1000;
    public static final int MIN_PRICE = 100;
    
    public static int buildComponent(byte manufacturer, byte quality,
                                     int price) {
        return price << (LG_NUM_C + LG_NUM_M) + quality << LG_NUM_M + 
            manufacturer;
    }
    
    public static long buildConfiguration(int processor, int motherboard,
                                            int gp) {
        return processor + (motherboard << C_SIZE) + (((long)gp + 1) << 
                                                      (C_SIZE << 1));
    }
    
    public static int getFP(long spec) {
        return (int)((spec & 0x3F) + (((spec >> C_SIZE) & 0x3F) << B_SIZE) +
            (((spec >>((C_SIZE << 1))) & 0x3F) << (B_SIZE << 1)));
    }
    
    public static int getPrice(long spec) {
        return (int)(((spec >> B_SIZE) & 0x3FF) + 
            ((spec >> (C_SIZE + B_SIZE)) & 0x3FF) +
            ((spec >> ((C_SIZE << 1) + B_SIZE)) & 0x3FF));
    }
    
    public static ArrayList<Long> matchConfiguration(int fp, int price,
        Hashtable<Integer, Integer> db) {
        //System.out.println(offerToString((long)fp + (price << (3 * B_SIZE))));
        ArrayList<Long> result = new ArrayList<Long>();
        for (Integer key : db.keySet()) {
            int p = db.get(key);
          //  System.out.println(offerToString((long)key + (p << (3 * B_SIZE))));
            if(((key | fp) == fp) && (p <= price)) {
                result.add((long)key + (p << (3 * B_SIZE)));
            }
        }
        return result;
    }
    
    public static ArrayList<ArrayList<Long>> matchTask(ArrayList<Long> spec, 
        Hashtable<Integer, Integer> db) {
        ArrayList<ArrayList<Long>> result = 
            new ArrayList<ArrayList<Long>>();
        for(Long item : spec) {
            result.add(matchConfiguration(getFP(item), getPrice(item), db));
        }
        return result;
    }
    
    public static Hashtable<Integer, Integer> generateConfigurations(int count) 
    {
        Hashtable<Integer,Integer> result = new Hashtable<Integer, Integer>();
        for (int i = 0; i < count; i++) {
            int fp = generateRandomFP(true);
            result.put(fp, generatePrice(fp));
        }
        return result;
    }

    private static int generateRandomFP(boolean exact) {
        return generateFPC(exact) + (generateFPC(exact) << B_SIZE) + 
            (generateFPC(exact) << (B_SIZE << 1));
    }

    private static int generatePrice(int fp) {
        return generatePriceComp(fp & 0x3F, 0) + 
            generatePriceComp((fp >> B_SIZE) & 0x3F, 1) +
            generatePriceComp((fp >> (B_SIZE << 1)) & 0x3F, 2);
    }
    
    public static ArrayList<Long> generateTask(int count) {
        ArrayList<Long> result = new ArrayList<Long>();
        for(int i = 0; i < count;i++) {
            result.add(generatePartialConf());
        }
        return result;
    }

    public static long generatePartialConf() {
        return buildConfiguration(generatePartialComp(), 
                                  generatePartialComp(), 
                                  generatePartialComp());
    }
          
    private static int generateFPC(boolean exact) {
        if(exact) {
            return (1 << rand.nextInt(3)) + (1 << (LG_NUM_C + rand.nextInt(3)));
        } else {
            return (generateRandFPC() + (generateRandFPC() << LG_NUM_C));
        }
    }

    private static int generateRandFPC() {
        if(rand.nextDouble() < P_CUT) {
            return C_NOT_SPECIFIED;
        } else {
            return 1 << rand.nextInt(3);
        }
    }

    private static int generatePriceComp(int componentDesc, int component) {
        return 100 + rand.nextInt(900);
    }
    
    public static String componentToString(int component, boolean withPrice) {
        String result = "(";
        switch(component & 0x7) {
        case INTEL : result += "INTEL";break;
        case HP: result += "HP";break;
        case COMP : result += "COMPQ";break;
        default : result += "NOT SPCIFIED";
        }
        result += ",";
        switch((component >> LG_NUM_M) & 0x7) {
        case AVG : result += "AVERAGE";break;
        case GOOD : result += "GOOD";break;
        case BEST : result += "BEST";break;
        default : result += "NOT SPECIFIED";
        }
        if(withPrice) {
            return result + "," + (component >> B_SIZE) + ")";
        } else {
            return result + ")";
        }
    }
    
    public static String configurationToString(long spec) {
        return "(" + componentToString((int)spec & 0xFFFF, true) + "," +
            componentToString((int)(spec >> C_SIZE) & 0xFFFF, true) + "," +
            componentToString((int)(spec >> (C_SIZE << 1)) & 0xFFFF, true) +
            ")";
    }

    public static String offerToString(long spec) {
        return "(" + componentToString((int)spec & 0x3F, false) + "," +
            componentToString((int)(spec >> B_SIZE) & 0x3F, false) + "," +
            componentToString((int)(spec >> (B_SIZE << 1)) & 0x3F, false) + ","
            + (spec >> (3 * B_SIZE)) + ")";
    }
    
    public static int generatePartialComp() {
        if(rand.nextDouble() < P_CUT) {
            return generateFPC(false) + (P_NOT_SPECIFIED << B_SIZE);
        } else {
            return generateFPC(false) + (generatePriceComp(0, 0) << B_SIZE);
        }
    }
}
